#include "../src/binlog/binlog_manager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <assert.h>
#include <time.h>

#define TEST_BINLOG_DIR "./test_binlog"
#define TEST_PREFIX "test_binlog"

// Test result tracking
static int tests_run = 0;
static int tests_passed = 0;

// Helper macros for testing
#define ASSERT(condition, message) do { \
    tests_run++; \
    if (condition) { \
        tests_passed++; \
        printf("✓ %s\n", message); \
    } else { \
        printf("✗ %s (line %d)\n", message, __LINE__); \
    } \
} while(0)

// Cleanup test directory
void cleanup_test_dir() {
    system("rm -rf " TEST_BINLOG_DIR);
}

// Create test directory
void setup_test_dir() {
    cleanup_test_dir();
    mkdir(TEST_BINLOG_DIR, 0755);
}

// Test binlog manager creation and destruction
void test_binlog_manager_lifecycle() {
    printf("\n=== Testing Binlog Manager Lifecycle ===\n");
    
    setup_test_dir();
    
    // Test creation
    binlog_manager_t* binlog = binlog_manager_create(TEST_BINLOG_DIR, TEST_PREFIX, 1);
    ASSERT(binlog != NULL, "Binlog manager created successfully");
    ASSERT(binlog->server_id == 1, "Server ID set correctly");
    ASSERT(strcmp(binlog->binlog_directory, TEST_BINLOG_DIR) == 0, "Binlog directory set correctly");
    ASSERT(strcmp(binlog->filename_prefix, TEST_PREFIX) == 0, "Filename prefix set correctly");
    ASSERT(binlog->executed_gtids != NULL, "GTID set initialized");
    
    // Test configuration
    int result = binlog_set_max_file_size(binlog, 10 * 1024 * 1024);
    ASSERT(result == 0, "Max file size set successfully");
    ASSERT(binlog->max_file_size == 10 * 1024 * 1024, "Max file size value correct");
    
    result = binlog_set_server_id(binlog, 42);
    ASSERT(result == 0, "Server ID updated successfully");
    ASSERT(binlog->server_id == 42, "Server ID value updated");
    
    result = binlog_set_sync_mode(binlog, false);
    ASSERT(result == 0, "Sync mode set successfully");
    ASSERT(binlog->sync_binlog == false, "Sync mode value correct");
    
    result = binlog_set_expire_days(binlog, 30);
    ASSERT(result == 0, "Expire days set successfully");
    ASSERT(binlog->expire_logs_days == 30, "Expire days value correct");
    
    // Test destruction
    binlog_manager_destroy(binlog);
    printf("Binlog manager destroyed successfully\n");
    
    cleanup_test_dir();
}

// Test GTID set operations
void test_gtid_set_operations() {
    printf("\n=== Testing GTID Set Operations ===\n");
    
    // Create GTID set
    gtid_set_t* set = gtid_set_create();
    ASSERT(set != NULL, "GTID set created successfully");
    ASSERT(set->count == 0, "Initial GTID count is zero");
    
    // Create test GTIDs
    gtid_t gtid1;
    strcpy(gtid1.server_uuid, "550e8400-e29b-41d4-a716-446655440000");
    gtid1.transaction_id = 1;
    
    gtid_t gtid2;
    strcpy(gtid2.server_uuid, "550e8400-e29b-41d4-a716-446655440001");
    gtid2.transaction_id = 1;
    
    gtid_t gtid3;
    strcpy(gtid3.server_uuid, "550e8400-e29b-41d4-a716-446655440000");
    gtid3.transaction_id = 2;
    
    // Test adding GTIDs
    int result = gtid_set_add(set, &gtid1);
    ASSERT(result == 0, "First GTID added successfully");
    ASSERT(set->count == 1, "GTID count is 1");
    
    result = gtid_set_add(set, &gtid2);
    ASSERT(result == 0, "Second GTID added successfully");
    ASSERT(set->count == 2, "GTID count is 2");
    
    result = gtid_set_add(set, &gtid3);
    ASSERT(result == 0, "Third GTID added successfully");
    ASSERT(set->count == 3, "GTID count is 3");
    
    // Test duplicate addition
    result = gtid_set_add(set, &gtid1);
    ASSERT(result == 0, "Duplicate GTID handled correctly");
    ASSERT(set->count == 3, "GTID count unchanged for duplicate");
    
    // Test contains
    bool contains = gtid_set_contains(set, &gtid1);
    ASSERT(contains == true, "GTID set contains first GTID");
    
    contains = gtid_set_contains(set, &gtid2);
    ASSERT(contains == true, "GTID set contains second GTID");
    
    gtid_t gtid_not_exists;
    strcpy(gtid_not_exists.server_uuid, "550e8400-e29b-41d4-a716-446655440099");
    gtid_not_exists.transaction_id = 999;
    
    contains = gtid_set_contains(set, &gtid_not_exists);
    ASSERT(contains == false, "GTID set does not contain non-existent GTID");
    
    // Test string conversion
    char* gtid_string = gtid_set_to_string(set);
    ASSERT(gtid_string != NULL, "GTID set converted to string");
    printf("GTID set string: %s\n", gtid_string);
    free(gtid_string);
    
    // Test string parsing
    gtid_set_t* parsed_set = gtid_set_from_string("550e8400-e29b-41d4-a716-446655440000:1-5");
    ASSERT(parsed_set != NULL, "GTID set parsed from string");
    ASSERT(parsed_set->count == 1, "Parsed GTID set has correct count");
    
    gtid_set_destroy(parsed_set);
    gtid_set_destroy(set);
}

// Test event creation and management
void test_event_operations() {
    printf("\n=== Testing Event Operations ===\n");
    
    // Test query event data
    const char* test_query = "SELECT * FROM users WHERE id = 1";
    const char* test_db = "testdb";
    
    query_event_data_t query_data;
    query_data.thread_id = 123;
    query_data.exec_time = 500;
    query_data.error_code = 0;
    query_data.db_name_len = strlen(test_db);
    query_data.db_name = (char*)test_db;
    query_data.query_len = strlen(test_query);
    query_data.query = (char*)test_query;
    query_data.status_vars_len = 0;
    
    // Create query event
    binlog_event_t* event = binlog_event_create(QUERY_EVENT, &query_data, sizeof(query_data));
    ASSERT(event != NULL, "Query event created successfully");
    ASSERT(event->header.type_code == QUERY_EVENT, "Event type is correct");
    ASSERT(event->data_size == sizeof(query_data), "Event data size is correct");
    ASSERT(event->checksum != 0, "Event checksum calculated");
    
    // Test event copy
    binlog_event_t* copied_event = binlog_event_copy(event);
    ASSERT(copied_event != NULL, "Event copied successfully");
    ASSERT(copied_event->header.type_code == event->header.type_code, "Copied event type matches");
    ASSERT(copied_event->data_size == event->data_size, "Copied event data size matches");
    ASSERT(copied_event->checksum == event->checksum, "Copied event checksum matches");
    
    // Test XID event
    xid_event_data_t xid_data;
    xid_data.xid = 12345;
    
    binlog_event_t* xid_event = binlog_event_create(XID_EVENT, &xid_data, sizeof(xid_data));
    ASSERT(xid_event != NULL, "XID event created successfully");
    ASSERT(xid_event->header.type_code == XID_EVENT, "XID event type is correct");
    
    // Test GTID event
    gtid_event_data_t gtid_data;
    gtid_data.flags = 0;
    strcpy((char*)gtid_data.server_uuid, "test-uuid-binary");
    gtid_data.transaction_id = 999;
    gtid_data.logical_clock_type = 2;
    gtid_data.logical_clock = 0;
    
    binlog_event_t* gtid_event = binlog_event_create(GTID_LOG_EVENT, &gtid_data, sizeof(gtid_data));
    ASSERT(gtid_event != NULL, "GTID event created successfully");
    ASSERT(gtid_event->header.type_code == GTID_LOG_EVENT, "GTID event type is correct");
    
    // Test event type string conversion
    const char* type_str = binlog_event_type_to_string(QUERY_EVENT);
    ASSERT(strcmp(type_str, "QUERY_EVENT") == 0, "Event type string conversion works");
    
    type_str = binlog_event_type_to_string(XID_EVENT);
    ASSERT(strcmp(type_str, "XID_EVENT") == 0, "XID event type string conversion works");
    
    type_str = binlog_event_type_to_string(GTID_LOG_EVENT);
    ASSERT(strcmp(type_str, "GTID_LOG_EVENT") == 0, "GTID event type string conversion works");
    
    // Cleanup
    binlog_event_destroy(event);
    binlog_event_destroy(copied_event);
    binlog_event_destroy(xid_event);
    binlog_event_destroy(gtid_event);
}

// Test binlog file operations
void test_binlog_file_operations() {
    printf("\n=== Testing Binlog File Operations ===\n");
    
    setup_test_dir();
    
    // Create binlog manager
    binlog_manager_t* binlog = binlog_manager_create(TEST_BINLOG_DIR, TEST_PREFIX, 1);
    ASSERT(binlog != NULL, "Binlog manager created");
    
    // Start binlog manager (creates initial file)
    int result = binlog_manager_start(binlog);
    ASSERT(result == 0, "Binlog manager started successfully");
    ASSERT(binlog->active_file != NULL, "Active file created");
    ASSERT(binlog->active_file->is_active == true, "Active file marked as active");
    
    // Test writing events
    result = binlog_write_query_event(binlog, 123, "testdb", "SELECT 1", 100, 0);
    ASSERT(result == 0, "Query event written successfully");
    ASSERT(binlog->events_written == 2, "Event count includes format desc + query"); // +1 for format desc event
    
    result = binlog_write_xid_event(binlog, 12345);
    ASSERT(result == 0, "XID event written successfully");
    ASSERT(binlog->events_written == 3, "Event count updated");
    
    gtid_t test_gtid;
    strcpy(test_gtid.server_uuid, binlog->server_uuid);
    test_gtid.transaction_id = 1;
    
    result = binlog_write_gtid_event(binlog, &test_gtid);
    ASSERT(result == 0, "GTID event written successfully");
    ASSERT(binlog->events_written == 4, "Event count updated");
    
    // Check GTID was added to executed set
    bool contains = gtid_set_contains(binlog->executed_gtids, &test_gtid);
    ASSERT(contains == true, "GTID added to executed set");
    
    // Test file rotation
    uint64_t original_size = binlog->active_file->size;
    result = binlog_rotate_file(binlog);
    ASSERT(result == 0, "File rotation successful");
    ASSERT(binlog->files_rotated == 1, "Rotation count updated");
    
    // Test statistics
    uint64_t total_size = binlog_get_total_size(binlog);
    ASSERT(total_size > 0, "Total binlog size is positive");
    
    uint32_t file_count = binlog_get_file_count(binlog);
    ASSERT(file_count >= 2, "Multiple binlog files exist"); // Original + rotated
    
    // Stop binlog manager
    result = binlog_manager_stop(binlog);
    ASSERT(result == 0, "Binlog manager stopped successfully");
    
    binlog_manager_destroy(binlog);
    cleanup_test_dir();
}

// Test filename operations
void test_filename_operations() {
    printf("\n=== Testing Filename Operations ===\n");
    
    // Test filename generation
    char* filename = binlog_generate_filename("test", 123);
    ASSERT(filename != NULL, "Filename generated successfully");
    ASSERT(strcmp(filename, "test.000123") == 0, "Filename format is correct");
    
    // Test sequence parsing
    uint32_t sequence;
    int result = binlog_parse_filename("test.000123", &sequence);
    ASSERT(result == 0, "Sequence parsed successfully");
    ASSERT(sequence == 123, "Parsed sequence is correct");
    
    // Test invalid filename parsing
    result = binlog_parse_filename("invalid_filename", &sequence);
    ASSERT(result != 0, "Invalid filename parsing fails correctly");
    
    free(filename);
}

// Test CRC32 calculation
void test_crc32_calculation() {
    printf("\n=== Testing CRC32 Calculation ===\n");
    
    const char* test_data = "Hello, World!";
    uint32_t crc1 = binlog_calculate_crc32(test_data, strlen(test_data));
    uint32_t crc2 = binlog_calculate_crc32(test_data, strlen(test_data));
    
    ASSERT(crc1 != 0, "CRC32 calculated (non-zero)");
    ASSERT(crc1 == crc2, "CRC32 calculation is deterministic");
    
    const char* test_data2 = "Hello, World!!";
    uint32_t crc3 = binlog_calculate_crc32(test_data2, strlen(test_data2));
    ASSERT(crc1 != crc3, "Different data produces different CRC32");
}

// Test error handling
void test_error_handling() {
    printf("\n=== Testing Error Handling ===\n");
    
    // Test NULL parameter handling
    binlog_manager_t* null_binlog = binlog_manager_create(NULL, "test", 1);
    ASSERT(null_binlog == NULL, "NULL directory parameter handled");
    
    null_binlog = binlog_manager_create("test", NULL, 1);
    ASSERT(null_binlog == NULL, "NULL prefix parameter handled");
    
    // Test invalid operations on NULL manager
    int result = binlog_write_query_event(NULL, 1, "db", "query", 100, 0);
    ASSERT(result != 0, "NULL manager handled in write operation");
    
    result = binlog_set_max_file_size(NULL, 1024);
    ASSERT(result != 0, "NULL manager handled in configuration");
    
    // Test GTID set error handling
    gtid_set_t* null_set = gtid_set_create();
    gtid_t test_gtid;
    strcpy(test_gtid.server_uuid, "test-uuid");
    test_gtid.transaction_id = 1;
    
    result = gtid_set_add(NULL, &test_gtid);
    ASSERT(result != 0, "NULL GTID set handled");
    
    result = gtid_set_add(null_set, NULL);
    ASSERT(result != 0, "NULL GTID handled");
    
    bool contains = gtid_set_contains(NULL, &test_gtid);
    ASSERT(contains == false, "NULL GTID set contains check handled");
    
    gtid_set_destroy(null_set);
}

// Test performance and stress scenarios
void test_performance_scenarios() {
    printf("\n=== Testing Performance Scenarios ===\n");
    
    setup_test_dir();
    
    binlog_manager_t* binlog = binlog_manager_create(TEST_BINLOG_DIR, TEST_PREFIX, 1);
    ASSERT(binlog != NULL, "Binlog manager created for performance test");
    
    // Set small file size for testing rotation
    binlog_set_max_file_size(binlog, 64 * 1024); // 64KB
    
    int result = binlog_manager_start(binlog);
    ASSERT(result == 0, "Binlog manager started");
    
    // Write many events to test performance and rotation
    clock_t start_time = clock();
    const int num_events = 1000;
    
    for (int i = 0; i < num_events; i++) {
        char query[256];
        snprintf(query, sizeof(query), "SELECT * FROM table%d WHERE id = %d", i % 10, i);
        
        result = binlog_write_query_event(binlog, i % 100, "testdb", query, i % 1000, 0);
        if (result != 0) {
            break;
        }
        
        if (i % 100 == 0) {
            result = binlog_write_xid_event(binlog, i);
            if (result != 0) {
                break;
            }
        }
    }
    
    clock_t end_time = clock();
    double elapsed = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
    
    printf("Wrote %d events in %.2f seconds (%.0f events/sec)\n", 
           num_events, elapsed, num_events / elapsed);
    
    ASSERT(binlog->events_written > num_events, "All events written successfully");
    ASSERT(binlog->files_rotated > 0, "File rotation occurred during stress test");
    
    uint32_t file_count = binlog_get_file_count(binlog);
    printf("Created %u binlog files during stress test\n", file_count);
    ASSERT(file_count > 1, "Multiple files created due to rotation");
    
    result = binlog_manager_stop(binlog);
    ASSERT(result == 0, "Binlog manager stopped after stress test");
    
    binlog_manager_destroy(binlog);
    cleanup_test_dir();
}

// Print test statistics
void print_statistics(binlog_manager_t* binlog) {
    if (!binlog) return;
    
    printf("\n=== Binlog Statistics ===\n");
    binlog_manager_print_stats(binlog);
}

int main() {
    printf("GuDB Binlog Manager Test Suite\n");
    printf("==============================\n");
    
    // Run all tests
    test_binlog_manager_lifecycle();
    test_gtid_set_operations();
    test_event_operations();
    test_binlog_file_operations();
    test_filename_operations();
    test_crc32_calculation();
    test_error_handling();
    test_performance_scenarios();
    
    // Print results
    printf("\n" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "\n");
    printf("Test Results: %d/%d tests passed (%.1f%%)\n", 
           tests_passed, tests_run, (tests_passed * 100.0) / tests_run);
    
    if (tests_passed == tests_run) {
        printf("🎉 All tests passed!\n");
        return 0;
    } else {
        printf("❌ Some tests failed!\n");
        return 1;
    }
}